﻿#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//C语言文件操作
//磁盘（硬盘）上的⽂件是⽂件。但是在程序设计中，我们⼀般谈的⽂件有两种：程序⽂件、数据⽂件（从⽂件功能的⻆度来分类的）。
//数据保存在内存中时，随着程序的结束，在内存中的数据也会销毁，而数据如何才能实现持久化（程序退出后数据仍保留）呢？
//就要保存到硬盘（以文件的形式、数据库形式）

//文件名
//⼀个⽂件要有⼀个唯⼀的⽂件标识，以便用户识别和引⽤。
//⽂件名包含3部分：⽂件路径 + ⽂件名主⼲ + ⽂件后缀
//例如： c :\code\test.txt
//为了⽅便起⻅，⽂件标识常被称为⽂件名。

//文件指针
//FILE* pf;//⽂件指针变量 


//⽂件的打开和关闭
//ANSI C 规定使⽤ fopen 函数来打开⽂件， fclose 来关闭⽂件。
//int main()
//{
//	//  D:\\VS2022 c语言\\code\\test.txt  - 绝对路径
//	//  test.txt - 相对路径
//
//	//FILE* pf = fopen("D:\\VS2022 c语言\\code\\test.txt", "r");
//	//FILE* pf = fopen("test.txt", "r");// 当前目录下的 test.txt （"./"表示表示当前目录）
//	//FILE* pf = fopen("./test.txt", "r");
//	FILE* pf = fopen("../test.txt", "r");// "../"上级目录下的 text.txt
//	// ../../ 在文件路径中表示 上上级目录（即当前目录的父目录的父目录）
//
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	else
//	{
//		printf("打开文件成功\n");
//	}
//	//读文件
//	//……
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//"w" - 只写，建⽴⼀个新的⽂件（覆盖已有文件或创建新文件（仅在路径、权限等条件允许时））
//如果目标文件已存在，"w" 模式会清空文件内容（即覆盖原有数据），并将文件指针定位到开头，准备写入新内容。
//int main()
//{
//	FILE* pf = fopen("test.txt", "w");
//	
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	else
//	{
//		printf("打开文件成功\n");
//	}
//	//读文件
//	//……
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}


//文件的顺序读写
//1.fputc(字符输出（写）函数,适用于所有输出流)
//int fputc ( int character, FILE * stream );
//将一个字符写入流中并使位置指示器前进。字符会被写在流的内部位置指示器所指示的位置，然后该位置指示器会自动前进一位。
//若操作成功，会返回已写入的字符。 若发生写入错误，会返回 EOF 并设置错误指示器（ferror）。
//int main()
//{
//	FILE* pf = fopen("text.txt", "w");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//写文件
//	/*fputc('a', pf);
//	fputc('b', pf);
//	fputc('c', pf);
//	fputc('d', pf);*/
//
//	char ch = 0;
//	for (ch = 'a'; ch <= 'z'; ch++)
//	{
//		fputc(ch, pf);
//	}
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//2.fgetc（字符输⼊函数,适用于所有输入流）
//int fgetc ( FILE * stream );
//返回指定流的内部文件位置指示器当前所指向的字符。然后，内部文件位置指示器会前进到下一个字符。
//若成功，会返回读取到的字符（提升为整型值）。
//int main()
//{
//	FILE* pf = fopen("text.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//读文件
//	int ch = fgetc(pf);
//	printf("%c\n", ch);
//
//	ch = fgetc(pf);
//	printf("%c\n", ch);
//
//	ch = fgetc(pf);
//	printf("%c\n", ch);
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//3.fputs(文本行输出函数,使用所有输出流)
// int fputs ( const char * str, FILE * stream );
//将字符串写入流的操作是将指定的字符串（由 str 指向）写入到流中。该函数从指定的地址（str）开始复制，
// 直到遇到终止的空字符（'\0'），但该终止空字符不会被复制到流中。
//若成功，会返回一个非负值。 若出错，该函数返回 EOF 并设置错误指示器（ferror）。

//int main()
//{
//	FILE* pf = fopen("text.txt", "w");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//写文件
//	//测试写一行数据
//	fputs("hello world\n", pf);
//	fputs("haha\n", pf);
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//4.fgets(文本行输入函数，适用于所有输入流)
// char* fgets(char* str, int num, FILE * stream);
// num - 要复制到 str 中的最大字符数（包括终止空字符）
//从流中读取字符，并将其作为 字符串 存储到 str 中，直到读取了 num - 1 个字符(它需要存放一个终止符\0,即第num个字符存放
// \0,例如，要读5个字符，实际上读到的是4个字符+\0)

//int main()
//{
//	FILE* pf = fopen("text.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//读文件
//	//测试读一行数据
//	char buf[20] = { 0 };
//	fgets(buf, 20, pf);
//	printf("%s\n", buf);
//	fgets(buf, 20, pf);
//	printf("%s\n", buf);
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//5.fprintf(格式化输出函数,适用于所有输出流)
//int fprintf ( FILE * stream, const char * format, ... ); - 可变参数列表

//struct S
//{
//	char name[20];
//	int age;
//	float score;
//};
//int main()
//{
//	struct S s = { "zhangsan",20,95.5 };
//	FILE* pf = fopen("text.txt", "w");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//格式化的写入文件
//	fprintf(pf, "%s %d %f\n", s.name, s.age, s.score);
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//6.fscanf(格式化输⼊函数，适用于所有输入流)
//int fscanf ( FILE * stream, const char * format, ... );

//struct S
//{
//	char name[20];
//	int age;
//	float score;
//};
//int main()
//{
//	struct S s = { 0 };
//	FILE* pf = fopen("text.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//格式化的读取文件
//	fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));
//
//	//打印看数据
//	printf("%s %d %f\n", s.name, s.age, s.score);
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//int main()
//{
//	int ch = fgetc(stdin);
//	fputc(ch, stdout);
//
//	return 0;
//}

//struct S
//{
//	char name[20];
//	int age;
//	float score;
//};
//int main()
//{
//	struct S s = { 0 };
//	fscanf(stdin, "%s %d %f", s.name, &(s.age), &(s.score));
//	fprintf(stdout, "%s %d %f\n", s.name, s.age, s.score);
//
//	return 0;
//}

//7.fwrite(二进制输出,适用于文件输出流)
//size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

//测试二进制的写入函数fwrite
//struct S
//{
//	char name[20];
//	int age;
//	float score;
//};
//int main()
//{
//	struct S s = { "张三",20,95.5 };
//	FILE* pf = fopen("text.txt", "wb");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//写文件
//	fwrite(&s, sizeof(struct S), 1, pf);
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//8.fread(二进制输入，适用于文件输入流)
//size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

//测试二进制的写函数fread
//struct S
//{
//	char name[20];
//	int age;
//	float score;
//};
//int main()
//{
//	struct S s = { 0 };
//	FILE* pf = fopen("text.txt", "rb");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//读文件
//	fread(&s, sizeof(struct S), 1, pf);
//	printf("%s %d %f\n", s.name, s.age, s.score);
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//9.sprintf和sscanf
//int sprintf ( char * str, const char * format, ... );
//int sscanf ( const char * s, const char * format, ...);

//struct S
//{
//	char name[20];
//	int age;
//	float score;
//};
//int main()
//{
//	struct S s = { "zhangsan",20,95.5 };
//	char buf[100] = { 0 };
//	sprintf(buf, "%s %d %f", s.name, s.age, s.score);
//	printf("%s\n", buf);//按照字符串打印
//
//	struct S tmp = { 0 };
//	sscanf(buf, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));
//	printf("%s %d %f\n", tmp.name, tmp.age, tmp.score);//打印结构体数据
//
//	return 0;
//}

//文件的随机读写
//1.fseek
//根据⽂件指针的位置和偏移量来定位⽂件指针（⽂件内容的光标）
//int fseek ( FILE * stream, long int offset, int origin );
//int main()
//{
//	FILE* pf = fopen("text1.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//	}
//	else
//	{
//		int ch = fgetc(pf);
//		printf("%c\n", ch);//a
//		ch = fgetc(pf);
//		printf("%c\n", ch);//b
//		ch = fgetc(pf);
//		printf("%c\n", ch);//c
//
//		//如果继续往下读，必然是d
//		//但是我们调整一下，去读取：b
//		//fseek(pf, -2, SEEK_CUR);
//		fseek(pf, 1, SEEK_SET);
//		ch = fgetc(pf);
//		printf("%c\n", ch);//b
//
//		fclose(pf);
//		pf = NULL;
//	}
//	return 0;
//}

//2.ftell
//返回⽂件指针相对于起始位置的偏移量
//long int ftell ( FILE * stream );
//int main()
//{
//	FILE* pf = fopen("text1.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//	}
//	else
//	{
//		int ch = fgetc(pf);
//		printf("%c\n", ch);//a
//		ch = fgetc(pf);
//		printf("%c\n", ch);//b
//		ch = fgetc(pf);
//		printf("%c\n", ch);//c
//
//		//如果继续往下读，必然是d
//		//但是我们调整一下，去读取：b
//		//fseek(pf, -2, SEEK_CUR);
//		fseek(pf, 1, SEEK_SET);
//		ch = fgetc(pf);
//		printf("%c\n", ch);//b
//
//		printf("%d\n", ftell(pf));//2
//
//		fclose(pf);
//		pf = NULL;
//	}
//	return 0;
//}

//3.rewind
//让文件指针的位置回到文件的起始位置
//void rewind ( FILE * stream );
//int main()
//{
//	FILE* pf = fopen("text1.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//	}
//	else
//	{
//		int ch = fgetc(pf);
//		printf("%c\n", ch);//a
//		ch = fgetc(pf);
//		printf("%c\n", ch);//b
//		ch = fgetc(pf);
//		printf("%c\n", ch);//c
//
//		//如果继续往下读，必然是d
//		//但是我们调整一下，去读取：b
//		//fseek(pf, -2, SEEK_CUR);
//		fseek(pf, 1, SEEK_SET);
//		ch = fgetc(pf);
//		printf("%c\n", ch);//b
//
//		printf("%d\n", ftell(pf));//2
//		rewind(pf);
//		ch = fgetc(pf);
//		printf("%c\n", ch);//a
//
//		fclose(pf);
//		pf = NULL;
//	}
//	return 0;
//}

//二进制文件和文本文件
//数据在内存中以⼆进制的形式存储，如果不加转换的输出到外存的文件中，就是二进制文件。
//如果要求在外存上以ASCII码的形式存储，则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
//int main()
//{
//	int a = 10000;
//	FILE* pf = fopen("text1.txt", "wb");
//	fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//文件读取结束的判定
//1.被错误使用的feof
//int feof ( FILE * stream );
//feof 的作⽤是：当文件读取结束的时候，判断是读取结束的原因是否是：遇到文件尾结束。(检查文件结束指示器)
//如果与该流相关联的文件结束指示器已设置，则返回一个非零值。 否则，返回零。
//2.ferror
//int ferror ( FILE * stream );
//ferror 的作⽤是：当文件读取结束的时候，判断是读取结束的原因是否是：文件操作中发生了错误。(检查错误指示器)
//如果与该流相关联的错误指示器已设置，则返回一个非零值。否则，返回零值。
#include <stdio.h>
#include <stdlib.h>
//文本文件例子
//int main()
//{
//	int c; // 注意：int，⾮char，要求处理EOF 
//	FILE* fp = fopen("text1.txt", "r");
//	if (!fp) {
//		perror("File opening failed");
//		return EXIT_FAILURE;
//	}
//	//fgetc 当读取失败的时候或者遇到⽂件结束的时候，都会返回EOF 
//	while ((c = fgetc(fp)) != EOF) // 标准C I/O读取⽂件循环 
//	{
//		putchar(c);
//	}
//	//判断是什么原因结束的 
//	if (ferror(fp))
//		puts("I/O error when reading");
//	else if (feof(fp))
//		puts("End of file reached successfully");
//
//	fclose(fp);
//}

//二进制文件例子
//enum { SIZE = 5 };
//int main(void)
//{
//	double a[SIZE] = { 1.,2.,3.,4.,5. };
//	FILE* fp = fopen("text1.txt", "wb"); // 必须⽤⼆进制模式 
//	fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组 
//	fclose(fp);
//
//	double b[SIZE];
//	fp = fopen("text1.txt", "rb");
//	size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组 
//	if (ret_code == SIZE) {
//		puts("Array read successfully, contents: ");
//		for (int n = 0; n < SIZE; ++n)
//			printf("%f ", b[n]);
//		putchar('\n');
//	}
//	else 
//	{ // error handling
//		if (feof(fp))
//			printf("Error reading text1.txt: unexpected end of file\n");
//		else if (ferror(fp)) {
//			perror("Error reading text1.txt");
//		}
//	}
//
//	fclose(fp);
//}

//文件缓冲区
#include <stdio.h>
#include <windows.h>
//VS2022 WIN11环境测试 
int main()
{
	FILE* pf = fopen("text1.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区 
	printf("睡眠10秒-已经写数据了，打开text1.txt文件，发现文件没有内容\n");
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时，才将输出缓冲区的数据写到⽂件（磁盘） 
	//注：fflush 在⾼版本的VS上不能使⽤了 
	printf("再睡眠10秒-此时，再次打开text1.txt文件，文件有内容了\n");
	Sleep(10000);
	fclose(pf);
	//注：fclose在关闭⽂件的时候，也会刷新缓冲区 
	pf = NULL;
	return 0;
}
